package cn.bs.seckill.controller;


import cn.bs.seckill.config.AccessLimit;
import cn.bs.seckill.config.RedisConfig;
import cn.bs.seckill.exception.GlobalException;
import cn.bs.seckill.pojo.Orders;
import cn.bs.seckill.pojo.SeckillMessage;
import cn.bs.seckill.pojo.SeckillOrders;
import cn.bs.seckill.pojo.User;
import cn.bs.seckill.rabbitMQ.Sender;
import cn.bs.seckill.service.GoodsService;
import cn.bs.seckill.service.OrdersService;
import cn.bs.seckill.service.SeckillOrdersService;
import cn.bs.seckill.utils.RedisUtil;
import cn.bs.seckill.vo.GoodsVo;
import cn.bs.seckill.vo.RespBean;
import cn.bs.seckill.vo.RespBeanEnum;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import com.wf.captcha.ArithmeticCaptcha;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import org.thymeleaf.util.ListUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 秒杀页面
 * </p>
 *
 * @author 布索
 * @since 2022-03-01
 */
@Controller
@RequestMapping("/seckill")
public class SeckillGoodsController implements InitializingBean {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private SeckillOrdersService seckillOrdersService;

    @Autowired
    private OrdersService ordersService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisScript<Long> script;

    @Autowired
    private Sender sender;
    @Autowired
    private Gson gson;


    private Map<Long, Boolean> EmptyStockMap = new HashMap<>();


    @PostMapping("/{path}/doSeckill")
    @ResponseBody
    public RespBean doSeckill(User user, Long goodsId,@PathVariable String path) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //判断传入的路径是否正确
        Boolean pathCheck = ordersService.checkPath(user, goodsId, path);
        if (!pathCheck){
            return RespBean.error(RespBeanEnum.PATH_ERROR);
        }
        //判断是否重复抢购
        SeckillOrders seckillOrder = (SeckillOrders) valueOperations.get("order:" + user.getId() + ":" + goodsId);
        if (null != seckillOrder) {
            return RespBean.error(RespBeanEnum.REPEATE_ERROR);
        }
        //内存标记,减少Redis访问
        if (EmptyStockMap.get(goodsId)) {
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        //使用lua脚本
        Long stock = (Long) redisTemplate.execute(script, Collections.singletonList("seckillGoods" + goodsId), Collections.EMPTY_LIST);
        if (stock < 0) {
            EmptyStockMap.put(goodsId, true);
            valueOperations.increment("seckillGoods:" + goodsId);
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        // 加入对列
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        sender.seckillMessageSend(gson.toJson(seckillMessage));
        return RespBean.success(0);
    }


    /*@RequestMapping("/doSeckill")
    public String doSeckill(Model model, User user, Long goodsId) {
        GoodsVo goodsVoByGoodsId = goodsService.findGoodsVoByGoodsId(goodsId);
        //判断用户是否登录
        if (null == user) {
            return "login";
        }
        model.addAttribute("user", user);
        //判断用户是否重复购买
        SeckillOrders seckillOrder = (SeckillOrders) redisUtil.get("order:" + user.getId() + ":" + goodsId);
        if (null != seckillOrder) {
            //返回前端显示错误信息,这里需要再去响应枚举类里面添加错误信息
            model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMESSAGE());
            return "secKillFail";
        }
        if (EmptyStockMap.get(goodsId)){
            model.addAttribute("errmsg", RespBeanEnum.EMPTY_STOCK.getMESSAGE());
            return "secKillFail";
        }
        //预减库存,第一步让redis预加载资源,然后拿到订单过后就库存减一
       Long stock =(Long) redisTemplate.execute(script, Collections.singletonList("seckillGoods" + goodsId), Collections.EMPTY_LIST);
        if (stock < 1) {
            //如果没有库存就真就不再访问redis
            EmptyStockMap.put(goodsId,true);
            model.addAttribute("errmsg", RespBeanEnum.EMPTY_STOCK.getMESSAGE());
            return "secKillFail";
        }
        //没有问题就生成订单
        SeckillMessage seckillMessage = new SeckillMessage(user,goodsId);
        sender.seckillMessageSend(gson.toJson(seckillMessage));
        //返回前端显示
        model.addAttribute("order", seckillMessage);
        model.addAttribute("goods", goodsVoByGoodsId);
        return "orderDetail";


    }*/

    //获取秒杀结果 -1失败 0等待
    @GetMapping("/result")
    @ResponseBody
    public RespBean getResult(User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }
        Long orderId = seckillOrdersService.getResult(user, goodsId);
        return RespBean.success(orderId);
    }


    //初始化,类似于静态代码块的作用,将商品库存数量加载到redis
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> goodsVo = goodsService.findGoodsVo();
        if (ListUtils.isEmpty(goodsVo)) {
            return;
        }
        goodsVo.forEach(vo -> {
            redisTemplate.opsForValue().set("seckillGoods" + vo.getId(), vo.getStockCount());
            //如果还有库存就false
            EmptyStockMap.put(vo.getId(), false);
        });

    }

    /*  //判断库存是否充足
     GoodsVo goodsVoByGoodsId = goodsService.findGoodsVoByGoodsId(goodsId);
     if (goodsVoByGoodsId.getStockCount() < 1) {
         model.addAttribute("errmsg", RespBeanEnum.EMPTY_STOCK.getMESSAGE());
         return "secKillFail";
     }
     //判断用户是否重复购买
     //如果用户id和商品id有重复的,说明重复购买
    // SeckillOrders secKillOrder = seckil lOrdersService.getOne(new QueryWrapper<SeckillOrders>().eq("user_id", user.getId()).eq("goods_id", goodsId));

     //通过redis获取
     SeckillOrders seckillOrder = (SeckillOrders) redisUtil.get("order:" + user.getId() + ":" + goodsId);
     if (null != seckillOrder) {
         //返回前端显示错误信息,这里需要再去响应枚举类里面添加错误信息
         model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMESSAGE());
         return "secKillFail";
     }
     //没有问题就生成订单
     Orders order = ordersService.secKill(user,goodsVoByGoodsId);
     //返回前端显示
     model.addAttribute("order",order);
     model.addAttribute("goods",goodsVoByGoodsId);
     return "orderDetail";
 }*/
    //获取秒杀的接口
    @RequestMapping("/path")
    @ResponseBody
    @AccessLimit(second = 5, maxCount = 5, needLogin = true)
    public RespBean getPath(User user, Long goodsId, String captcha) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }

        //判断验证码是否正确
        Boolean check = ordersService.checkCptcha(user, goodsId, captcha);
        if (!check) {
            return RespBean.error(RespBeanEnum.VERIFICARION_CODE_ERROR);
        }

        //生成秒杀的接口
        String str = ordersService.createPath(user, goodsId);
        return RespBean.success(str);
    }

    //生成验证码
    @RequestMapping("/captcha")
    public void vertifyCode(User user, Long goodsId, HttpServletResponse response) {
        if (user == null || goodsId < 0) {
            throw new GlobalException(RespBeanEnum.ERROR);
        }

        //设置请求头为输出图片数据
        response.setContentType("image/jpg");
        response.setHeader("Pargam", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        //生成验证码，将结果放入redis中
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, captcha.text(), 300, TimeUnit.SECONDS);
        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
